#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include "lab6.h"

#define ALPHA (0.125)
#define BETA (0.25)

#define TIME_TO_WAIT (15000)

#define WINDOW_SIZE_CAP (2000)

uint32_t sequence_number;
uint32_t ack_number;
uint32_t rtt;
int32_t dev;
uint32_t timeout_factor;

int max_window;

int closed;

/* Use this to keep track of the contents of your sliding window. */
inflight_packet window[WINDOW_SIZE_CAP];

/* The following three helper functions make dealing with time values a bit
 * easier.  You probably don't need to modify them, but you can if you want. */
int timeval_to_msec(struct timeval *t) {
    return t->tv_sec*1000+t->tv_usec/1000;
}

void msec_to_timeval(int millis, struct timeval *out_timeval) {
    out_timeval->tv_sec = millis/1000;
    out_timeval->tv_usec = (millis%1000)*1000;
}

int current_msec() {
    struct timeval t;
    gettimeofday(&t,0);
    return timeval_to_msec(&t);
}
/* End time functions. */


int my_connect(int socket,struct sockaddr_in *toaddr,int addrsize) {
    closed = 0;
    return connect(socket,(struct sockaddr*)toaddr,addrsize);
}

int my_rtt(int sock) {
    return rtt;
}

/* The value this returns (max_window) should be the value of the largest
 * window size your library uses over the course of the run. */
int my_max_window(int sock) {
    return max_window;
}

int abs(int value) {
    if (value < 0) {
        return -(value);
    }
    return value;
}

void sample_rtt(int before, int after) {
    int sample = after - before;
    rtt = ((1.0 - ALPHA) * rtt) + (ALPHA * sample);
    dev = ((1.0 - BETA) * dev) + (BETA * abs(sample - rtt));
}

void my_rto(struct timeval *timeval) {
    int timeout = (rtt + (4 * dev)) * timeout_factor;

    /* I don't have all day.  Cap the max timeout at three seconds. */
    if (timeout > 3000 || timeout < 0) {
        timeout = 3000;
    }

    msec_to_timeval(timeout, timeval);
}

void my_send(int sock, void *buf, size_t len)
{
    int attempts = 0;
    struct timeval timeout;

    /* Buffer for the packet. */
    char packet[MAX_PACKET];
    memset(packet, 0, sizeof(packet));

    struct lab6_hdr *hdr = (struct lab6_hdr *) packet;

    /* Set the header fields as needed. */
    hdr->sequence_number = htonl(sequence_number);

    if (buf == NULL) {
        /* It's the end, set fin flag. */
        hdr->fin = 1;
    } else {
        /* Copy the payload into our packet.  hdr + 1 is the address of the
         * first byte after the header. */
        memcpy(hdr+1,buf,len);
    }

    while (1) {
        fd_set rfds;
        int before, after;
        int selectval;

        FD_ZERO(&rfds);
        FD_SET(sock, &rfds);

        my_rto(&timeout);

        attempts += 1;
        before = current_msec();
        send(sock, packet, sizeof(struct lab6_hdr) + len, 0);

        selectval = select(sock+1, &rfds, NULL, NULL, &timeout);
        after = current_msec();
        switch (selectval) {
            char response[sizeof(struct lab6_hdr)];
            case -1:
                perror("select");
                exit(1);
                break;
            case 0:
                /* Timed out. */
                if (timeout_factor < 32768)
                    timeout_factor *= 2;
                break;
            case 1:
                memset(response, 0, sizeof(struct lab6_hdr));

                recv(sock, response, sizeof(struct lab6_hdr), 0);

                struct lab6_hdr *hdr = (struct lab6_hdr *) response;

                if (ntohl(hdr->ack_number) == sequence_number) {
                    if (attempts == 1) {
                        /* Only take a sample if there was no timeout. */
                        sample_rtt(before, after);
                        timeout_factor = 1;
                    }
                    sequence_number++;
                    return;
                }
                break;
        }
    }
}

int my_socket(int domain, int type, int protocol) {
    /* Initialize state. */
    sequence_number = 0;
    ack_number = 0;
    rtt = 500;
    dev = 1;
    timeout_factor = 1;
    max_window = 1;

    /* Initialize the window however you like after this. */
    memset(window, 0, WINDOW_SIZE_CAP * sizeof(inflight_packet));

    return socket(domain, type, protocol);
}

void send_ack(int sock, uint32_t seqno) {
    char ack[sizeof(struct lab6_hdr)];
    memset(ack, 0, sizeof(struct lab6_hdr));

    struct lab6_hdr *hdr = (struct lab6_hdr *) ack;
    hdr->ack_number = seqno;

    send(sock, ack, sizeof(ack), 0);
}

int my_recv(int sock, void * buffer, size_t length) {
    while (1) {
        /* Buffer for the packet. */
        char packet[MAX_PACKET];
        memset(packet, 0, sizeof(packet));

        struct lab6_hdr *hdr = (struct lab6_hdr *) packet;

        struct sockaddr_in fromaddr;
        socklen_t addrlen = sizeof(fromaddr);
        int recv_count = recvfrom(sock, packet, MAX_PACKET, 0,
                (struct sockaddr *) &fromaddr, &addrlen);

        /* Associate this UDP socket with a remote address.  This is simply
         * for convenience so that we can use send() rather than sendto(), since
         * sendto() requires an additional argument that we don't want to deal with
         * every time we need to send something. */
        if(connect(sock, (struct sockaddr *) &fromaddr, addrlen)) {
            perror("connect (my_recv)");
        }

        fprintf(stderr, "Got packet %d\n", ntohl(hdr->sequence_number));

        /* Send an ack. */
        send_ack(sock, hdr->sequence_number);

        if (ntohl(hdr->sequence_number) == sequence_number) {
            sequence_number += 1;
            /* Copy the payload into the user-supplied buffer. */
            memcpy(buffer, packet + sizeof(struct lab6_hdr),
                    recv_count - sizeof(struct lab6_hdr));

            if (hdr->fin) {
                closed = 1;
            }

            return recv_count - sizeof(struct lab6_hdr);
        }
    }
}

int my_close(int sock) {
    /* If we've already closed the socket, enter the time wait. */
    if (closed) {
        fd_set rfds;
        int select_result = 1;

        fprintf(stderr, "Entering time wait for %d ms.\n", TIME_TO_WAIT);

        while (select_result > 0) {
            FD_ZERO(&rfds);
            FD_SET(sock, &rfds);
            struct timeval timeout;
            char fin_packet[sizeof(struct lab6_hdr)];
            memset(fin_packet, 0, sizeof(struct lab6_hdr));

            msec_to_timeval(TIME_TO_WAIT, &timeout);
            select_result = select(sock+1, &rfds, NULL, NULL, &timeout);

            if (select_result == 1) {
                recv(sock, fin_packet, sizeof(struct lab6_hdr), 0);
                struct lab6_hdr *hdr = (struct lab6_hdr *) fin_packet;
                if (!hdr->fin) {
                    fprintf(stderr, "Got a non-fin packet in close?\n");
                } else {
                    fprintf(stderr,
                        "Got another fin, resetting timer to %d ms.\n",
                        TIME_TO_WAIT);
                }
                send_ack(sock, hdr->sequence_number);

            }
        }

        return close(sock);
    }

    /* Send an empty packet to signal end of file. */
    my_send(sock, NULL, 0);

    closed = 1;

    return close(sock);
}
